\ExplSyntaxOn

\NewDocumentCommand {\DndCRExp} { m }
  {
    \str_case_e:nnF {#1}
      {
        {0} {#1~(0~\xpname)}
        {1/10} {0~(10~\xpname)}
        {1/8} {#1~(25~\xpname)}
        {1/4} {#1~(50~\xpname)}
        {1/2} {#1~(100~\xpname)}
        {1} {#1~(200~\xpname)}
        {2} {#1~(450~\xpname)}
        {3} {#1~(700~\xpname)}
        {4} {#1~(\numprint{1100}~\xpname)}
        {5} {#1~(\numprint{1800}~\xpname)}
        {6} {#1~(\numprint{2300}~\xpname)}
        {7} {#1~(\numprint{2900}~\xpname)}
        {8} {#1~(\numprint{3900}~\xpname)}
        {9} {#1~(\numprint{5000}~\xpname)}
        {10} {#1~(\numprint{5900}~\xpname)}
        {11} {#1~(\numprint{7200}~\xpname)}
        {12} {#1~(\numprint{8400}~\xpname)}
        {13} {#1~(\numprint{10000}~\xpname)}
        {14} {#1~(\numprint{11500}~\xpname)}
        {15} {#1~(\numprint{13000}~\xpname)}
        {16} {#1~(\numprint{15000}~\xpname)}
        {17} {#1~(\numprint{18000}~\xpname)}
        {18} {#1~(\numprint{20000}~\xpname)}
        {19} {#1~(\numprint{22000}~\xpname)}
        {20} {#1~(\numprint{25000}~\xpname)}
        {21} {#1~(\numprint{33000}~\xpname)}
        {22} {#1~(\numprint{41000}~\xpname)}
        {23} {#1~(\numprint{50000}~\xpname)}
        {24} {#1~(\numprint{62000}~\xpname)}
        {25} {#1~(\numprint{75000}~\xpname)}
        {26} {#1~(\numprint{90000}~\xpname)}
        {27} {#1~(\numprint{105000}~\xpname)}
        {28} {#1~(\numprint{120000}~\xpname)}
        {29} {#1~(\numprint{135000}~\xpname)}
        {30} {#1~(\numprint{155000}~\xpname)}
      }
      {#1}
  }

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Monster environments
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Stat block made to look like the Monster Manual NPC definitions
\DeclareTColorBox {DndMonsterNoBg} { O{} m }
  {
    enhanced,
    frame~hidden,
    boxrule     = 0pt,
    breakable,
    parbox      = false,
    boxsep      = 0pt,
    toptitle    = 3mm,
    left        = 0pt,
    right       = 0pt,
    sharp~corners,
    opacityback = 0,
    colframe    = titlered,
    fonttitle   = \DndFontStatBlockTitle,
    fontupper   = \DndFontStatBlockBody,
    fontlower   = \DndFontStatBlockBody,
    title       = {#2},
    coltitle    = titlered,
    #1
  }

% Stat block made to look like stat blocks in the PHB.
\DeclareTColorBox {DndMonsterBg} { O{} m }
  {
    enhanced,
    frame~hidden,
    before~skip      = 12pt plus 3pt minus 3pt,
    boxrule          = 0pt,
    breakable,
    parbox           = false,
    boxsep           = 2pt,
    toptitle         = 8pt,
    top              = 0pt,
    left             = 2pt,
    right            = 2pt,
    bottom           = 7pt,
    sharp~corners,
    oversize         = 0pt,
    borderline~north = {3pt} {0pt}   {titlered},
    borderline~north = {2pt} {0.5pt} {statblockribbon},
    borderline~south = {3pt} {0pt}   {titlered},
    borderline~south = {2pt} {0.5pt} {statblockribbon},
    colback          = statblockbg,
    colbacktitle     = statblockbg,
    colframe         = titlered,
    fonttitle        = \DndFontStatBlockTitle,
    coltitle         = titlered,
    fontupper        = \DndFontStatBlockBody,
    fontlower        = \DndFontStatBlockBody,
    title            = {#2},
    #1
  }

% Alias DndMonster to correct background type
\bool_if:NTF \l__dnd_show_background_bool
  {
    \let\DndMonster\DndMonsterBg
    \let\endDndMonster\endDndMonsterBg
  }{
    \let\DndMonster\DndMonsterNoBg
    \let\endDndMonster\endDndMonsterNoBg
  }

% Italicized text appearing immediately after monster's name
\NewDocumentCommand {\DndMonsterType} {m}
  {
    \begin {hangingpar}
      \textit {#1}
    \end {hangingpar}
  }

% Fancy DnD 5e stat block rule
\NewDocumentCommand {\DndMonsterLine} {}
  {
    \par \vspace{-2pt} \noindent
    \begin {tikzpicture}
      \draw [ rulered, fill = rulered ] ( 0, 0 ) -- ( 0, 0.1 ) -- ( \linewidth, 0.05 );
    \end {tikzpicture}
    \par
  }

% A description variant used to list creature attributes.
\newlist {__dnd_monster_attributes} {description} {1}
\setlist [__dnd_monster_attributes]
  {
    before   = \color {titlered},
    font     = \DndFontStatBlockBody,
    labelsep = \l__dnd_space_dim,
    nosep,
  }

% Only prints the item label if the value was supplied
\cs_new_protected_nopar:Npn \__dnd_if_monster_attribute:nn #1#2
  {
    \tl_if_empty:NF {#1}
      { \item [#2] #1 }
  }

%% Monster basics
\keys_define:nn { dnd / monster / basics }
  {
    armor-class .tl_set:N          = \l__armor_class_tl,
    armor-class .initial:n         = 10,
    armor-class .value_required:n  = true,
    armorclass .meta:n = { armor-class = {#1} },
    hit-points .tl_set:N         = \l__hit_points_tl,
    hit-points .initial:n        = \DndDice {1d8},
    hit-points .value_required:n = true,
    hitpoints .meta:n = { hit-points = {#1} },
    speed .tl_set:N         = \l__speed_tl,
    speed .initial:n        = 30 ~ \unitsname,
    speed .value_required:n = true,
  }

\cs_new_protected_nopar:Npn \__dnd_monster_basics:
  {
      \begin {__dnd_monster_attributes}
        \item [\armorclassname] \l__armor_class_tl
        \item [\hitpointsname]  \l__hit_points_tl
        \item [\speedname]      \l__speed_tl
      \end {__dnd_monster_attributes}
  }

\NewDocumentCommand {\DndMonsterBasics} {o}
  {
    \group_begin:
      \keys_set:nn { dnd / monster / basics } {#1}
      \DndMonsterLine
      \__dnd_monster_basics:
      \DndMonsterLine
    \group_end:
  }

%% Monster CR/XP helper
% \l_tmpa_tl - CR
% \l_tmpb_tl - XP
\cs_new_protected_nopar:Npn \__dnd_cr_xp:N #1
  {
    \tl_set:Nn \l_tmpa_tl {#1}
    \str_case_e:nnTF {#1}
      {
        {0}   { \tl_set:Nn \l_tmpb_tl {0} }
        {0/10}
          {
            \tl_set:Nn \l_tmpa_tl {0}
            \tl_set:Nn \l_tmpb_tl {10}
          }
        {1/10}
          {
            \dnd@deprecate{ Challenge ~ 1/10 } {0.8} [ Use ~ challenge ~ 0/10 ~ instead. ]
            \tl_set:Nn \l_tmpa_tl {0}
            \tl_set:Nn \l_tmpb_tl {10}
          }
        {1/8} { \tl_set:Nn \l_tmpb_tl {25} }
        {1/4} { \tl_set:Nn \l_tmpb_tl {50} }
        {1/2} { \tl_set:Nn \l_tmpb_tl {100} }
        {1}   { \tl_set:Nn \l_tmpb_tl {200} }
        {2}   { \tl_set:Nn \l_tmpb_tl {450} }
        {3}   { \tl_set:Nn \l_tmpb_tl {700} }
        {4}   { \tl_set:Nn \l_tmpb_tl {1100} }
        {5}   { \tl_set:Nn \l_tmpb_tl {1800} }
        {6}   { \tl_set:Nn \l_tmpb_tl {2300} }
        {7}   { \tl_set:Nn \l_tmpb_tl {2900} }
        {8}   { \tl_set:Nn \l_tmpb_tl {3900} }
        {9}   { \tl_set:Nn \l_tmpb_tl {5000} }
        {10}  { \tl_set:Nn \l_tmpb_tl {5900} }
        {11}  { \tl_set:Nn \l_tmpb_tl {7200} }
        {12}  { \tl_set:Nn \l_tmpb_tl {8400} }
        {13}  { \tl_set:Nn \l_tmpb_tl {10000} }
        {14}  { \tl_set:Nn \l_tmpb_tl {11500} }
        {15}  { \tl_set:Nn \l_tmpb_tl {13000} }
        {16}  { \tl_set:Nn \l_tmpb_tl {15000} }
        {17}  { \tl_set:Nn \l_tmpb_tl {18000} }
        {18}  { \tl_set:Nn \l_tmpb_tl {20000} }
        {19}  { \tl_set:Nn \l_tmpb_tl {22000} }
        {20}  { \tl_set:Nn \l_tmpb_tl {25000} }
        {21}  { \tl_set:Nn \l_tmpb_tl {33000} }
        {22}  { \tl_set:Nn \l_tmpb_tl {41000} }
        {23}  { \tl_set:Nn \l_tmpb_tl {50000} }
        {24}  { \tl_set:Nn \l_tmpb_tl {62000} }
        {25}  { \tl_set:Nn \l_tmpb_tl {75000} }
        {26}  { \tl_set:Nn \l_tmpb_tl {90000} }
        {27}  { \tl_set:Nn \l_tmpb_tl {105000} }
        {28}  { \tl_set:Nn \l_tmpb_tl {120000} }
        {29}  { \tl_set:Nn \l_tmpb_tl {135000} }
        {30}  { \tl_set:Nn \l_tmpb_tl {155000} }
      }
      { \l_tmpa_tl\ ( \numprint {\l_tmpb_tl} ~ \xpname ) }
      { #1 }
}

%% Monster CR/PB helper
% \l_tmpa_tl - CR
% \l_tmpb_tl - PB
\cs_new_protected_nopar:Npn \__dnd_cr_pb:N #1
  {
    \tl_set:Nn \l_tmpa_tl {#1}
    \str_case_e:nnTF {#1}
      {
        {0}   { \tl_set:Nn \l_tmpb_tl {+2} }
        {1/8} { \tl_set:Nn \l_tmpb_tl {+2} }
        {1/4} { \tl_set:Nn \l_tmpb_tl {+2} }
        {1/2} { \tl_set:Nn \l_tmpb_tl {+2} }
        {1}   { \tl_set:Nn \l_tmpb_tl {+2} }
        {2}   { \tl_set:Nn \l_tmpb_tl {+2} }
        {3}   { \tl_set:Nn \l_tmpb_tl {+2} }
        {4}   { \tl_set:Nn \l_tmpb_tl {+2} }
        {5}   { \tl_set:Nn \l_tmpb_tl {+3} }
        {6}   { \tl_set:Nn \l_tmpb_tl {+3} }
        {7}   { \tl_set:Nn \l_tmpb_tl {+3} }
        {8}   { \tl_set:Nn \l_tmpb_tl {+3} }
        {9}   { \tl_set:Nn \l_tmpb_tl {+4} }
        {10}  { \tl_set:Nn \l_tmpb_tl {+4} }
        {11}  { \tl_set:Nn \l_tmpb_tl {+4} }
        {12}  { \tl_set:Nn \l_tmpb_tl {+4} }
        {13}  { \tl_set:Nn \l_tmpb_tl {+5} }
        {14}  { \tl_set:Nn \l_tmpb_tl {+5} }
        {15}  { \tl_set:Nn \l_tmpb_tl {+5} }
        {16}  { \tl_set:Nn \l_tmpb_tl {+5} }
        {17}  { \tl_set:Nn \l_tmpb_tl {+6} }
        {18}  { \tl_set:Nn \l_tmpb_tl {+6} }
        {19}  { \tl_set:Nn \l_tmpb_tl {+6} }
        {20}  { \tl_set:Nn \l_tmpb_tl {+6} }
        {21}  { \tl_set:Nn \l_tmpb_tl {+7} }
        {22}  { \tl_set:Nn \l_tmpb_tl {+7} }
        {23}  { \tl_set:Nn \l_tmpb_tl {+7} }
        {24}  { \tl_set:Nn \l_tmpb_tl {+7} }
        {25}  { \tl_set:Nn \l_tmpb_tl {+8} }
        {26}  { \tl_set:Nn \l_tmpb_tl {+8} }
        {27}  { \tl_set:Nn \l_tmpb_tl {+8} }
        {28}  { \tl_set:Nn \l_tmpb_tl {+8} }
        {29}  { \tl_set:Nn \l_tmpb_tl {+9} }
        {30}  { \tl_set:Nn \l_tmpb_tl {+9} }
      }
      { \l_tmpb_tl }
      { #1 }
  }

%% Monster details
\keys_define:nn { dnd / monster / details }
{
  saving-throws .tl_set:N         = \l__saving_throws_tl,
  saving-throws .value_required:n = true,
  savingthrows .meta:n = { saving-throws = {#1} },
  skills .tl_set:N         = \l__skills_tl,
  skills .value_required:n = true,
  damage-vulnerabilities .tl_set:N         = \l__damage_vulnerabilities_tl,
  damage-vulnerabilities .value_required:n = true,
  damagevulnerabilities .meta:n = { damage-vulnerabilities = {#1} },
  damage-resistances .tl_set:N         = \l__damage_resistances_tl,
  damage-resistances .value_required:n = true,
  damageresistances .meta:n = { damage-resistances = {#1} },
  damage-immunities .tl_set:N         = \l__damage_immunities_tl,
  damage-immunities .value_required:n = true,
  damageimmunities .meta:n = { damage-immunities = {#1} },
  condition-immunities .tl_set:N         = \l__condition_immunities_tl,
  condition-immunities .value_required:n = true,
  conditionimmunities .meta:n = { condition-immunities = {#1} },
  senses .tl_set:N         = \l__senses_tl,
  senses .initial:n        = \defaultsensesname,
  senses .value_required:n = true,
  languages .tl_set:N         = \l__languages_tl,
  languages .initial:n        = ---,
  languages .value_required:n = true,
  challenge .tl_set:N         = \l__challenge_tl,
  challenge .initial:n        = 1,
  challenge .value_required:n = true,
  proficiency-bonus .tl_set:N = \l__proficiency_tl,
  proficiency-bonus .value_required:n = false,
  proficiency-bonus .default:n = *,
  proficiencybonus .meta:n    = { proficiency-bonus = {#1} },
}

\cs_new_protected_nopar:Npn \__dnd_monster_details:
{
    \begin {__dnd_monster_attributes}
      \__dnd_if_monster_attribute:nn {\l__saving_throws_tl}          {\savesname}
      \__dnd_if_monster_attribute:nn {\l__skills_tl}                 {\skillsname}
      \__dnd_if_monster_attribute:nn {\l__damage_vulnerabilities_tl} {\dvulname}
      \__dnd_if_monster_attribute:nn {\l__damage_resistances_tl}     {\dresname}
      \__dnd_if_monster_attribute:nn {\l__damage_immunities_tl}      {\dimmname}
      \__dnd_if_monster_attribute:nn {\l__condition_immunities_tl}   {\cimmname}
      \item [\sensesname]    \l__senses_tl
      \item [\languagesname] \l__languages_tl
      \item [\challengename] \__dnd_cr_xp:N {\l__challenge_tl}
      \tl_if_empty:NF {\l__proficiency_tl}
        {
          {\enit@svlabel{\enit@format{\pbonname}}}
          \hskip \labelsep
          \str_if_eq:VnTF \l__proficiency_tl *
            {\__dnd_cr_pb:N {\l__challenge_tl}}
            {\l__proficiency_tl}
        }
    \end {__dnd_monster_attributes}
}

\NewDocumentCommand {\DndMonsterDetails} {o}
{
  \group_begin:
    \keys_set:nn { dnd / monster / details } {#1}
    \DndMonsterLine{}
    \__dnd_monster_details:
    \DndMonsterLine{}
  \group_end:
}

%% Ability scores
% Print ability score stats with auto-computed modifier
% e.g. \stat{12} prints "12 (+1)"
\cs_new_protected_nopar:Npn \__dnd_ability_score_modifier:N #1
  {
    \regex_match:NnTF \c__pos_int_regex {#1}
      {
        \int_set:Nn \l_tmpa_int { \fp_eval:n { floor ( ( #1 - 10 ) / 2 ) } }

        #1 ~ (
        \int_compare:nNnTF {\l_tmpa_int} < {0}
        { -- }
        {+}
        \int_abs:n \l_tmpa_int )
      }
      {#1}
  }

\keys_define:nn { dnd / monster / ability_scores }
  {
    str .tl_set:N         = \l__str_tl,
    str .initial:n        = 10,
    str .value_required:n = true,
    STR .meta:n = { str = #1 },
    dex .tl_set:N         = \l__dex_tl,
    dex .initial:n        = 10,
    dex .value_required:n = true,
    DEX .meta:n = { dex = #1 },
    con .tl_set:N         = \l__con_tl,
    con .initial:n        = 10,
    con .value_required:n = true,
    CON .meta:n = { con = #1 },
    int .tl_set:N         = \l__int_tl,
    int .initial:n        = 10,
    int .value_required:n = true,
    INT .meta:n = { int = #1 },
    wis .tl_set:N         = \l__wis_tl,
    wis .initial:n        = 10,
    wis .value_required:n = true,
    WIS .meta:n = { wis = #1 },
    cha .tl_set:N         = \l__cha_tl,
    cha .initial:n        = 10,
    cha .value_required:n = true,
    CHA .meta:n = { cha = #1 },
  }

% Ability scores in a table
\cs_new_protected_nopar:Npn \__dnd_monster_ability_scores:
  {
    \color {titlered}
    \par \vspace{1pt} \noindent
    \begin{tabularx} {\linewidth} {YYYYYY}
      \rule {0pt} {3.7mm} % adds space between hline and table
      \textbf {\strstatname} &
      \textbf {\dexstatname} &
      \textbf {\constatname} &
      \textbf {\intstatname} &
      \textbf {\wisstatname} &
      \textbf {\chastatname} \\
      \exp_args:NV \__dnd_ability_score_modifier:N \l__str_tl &
      \exp_args:NV \__dnd_ability_score_modifier:N \l__dex_tl &
      \exp_args:NV \__dnd_ability_score_modifier:N \l__con_tl &
      \exp_args:NV \__dnd_ability_score_modifier:N \l__int_tl &
      \exp_args:NV \__dnd_ability_score_modifier:N \l__wis_tl &
      \exp_args:NV \__dnd_ability_score_modifier:N \l__cha_tl
    \end{tabularx}

    \par \vspace{4pt}%
  }

\NewDocumentCommand {\DndMonsterAbilityScores} {o}
  {
    \group_begin:
      \keys_set:nn { dnd / monster / ability_scores } {#1}
      \__dnd_monster_ability_scores:
    \group_end:
  }

% Inline header for monster actions - similar to a paragraph
\NewDocumentCommand {\DndMonsterAction} {m}
  { \par \smallskip \noindent \textsl { \textbf {#1.} } }

% Inline header for monster sub actions - similar to a subparagraph
\NewDocumentCommand {\DndMonsterSubAction} {m}
  { \par \textsl { \textbf {#1.} } }

% Monster subsection header style
\NewDocumentCommand {\DndMonsterSection} {m}
  {
    \addvspace{6pt plus 2pt minus 2pt} \noindent
    \group_begin:
      \DndFontStatBlockSection #1\nopagebreak[4]
      % \rule is a horizontal command, so placing it under the title incurs extra
      % line spacing. Use \hrule (a vertical command) instead.
      \vspace {2pt}\nopagebreak[4]
      \hrule height 0.6pt
    \group_end:
      \par \vspace{5pt}
    \noindent \ignorespaces
  }

% Spellcasting levels
\newlist {DndMonsterSpells} {description} {1}
\setlist [DndMonsterSpells]
  {
    font     = \normalfont \DndFontStatBlockBody,
    labelsep = \l__dnd_space_dim,
    noitemsep,
    topsep   = 6pt plus 2pt minus 2pt,
  }

\NewDocumentCommand {\DndEmphSpellString} {m}
  {
    \group_begin:
      \seq_set_from_clist:Nn \l_tmpa_seq {#1}
      \seq_set_map:NNn \l_tmpb_seq \l_tmpa_seq { \exp_not:n { \emph {##1} } }
      \seq_use:Nn \l_tmpb_seq { ,~ }
    \group_end:
  }

\NewDocumentCommand {\DndInnateSpellLevel} { O {\innateatwillname} m }
  {
    \item
      [
        \regex_match:NnTF \c__pos_int_regex {#1}
        {
          \str_if_in:NnTF {#2} {,}
          { \__dnd_caption:nn {\numberperdayeachname} {#1} }
          { \__dnd_caption:nn {\numberperdayname} {#1} }
        }
        {#1}
        :
      ]
      \DndEmphSpellString {#2}
  }

\NewDocumentCommand {\DndMonsterSpellLevel} { O{\spellcantripsname} O{\spellatwillname} m }
  {
    \item
      [
        \str_case_e:nnF {#1}
          {
            {1} {\spellfirstlevelname}
            {2} {\spellsecondlevelname}
            {3} {\spellthirdlevelname}
            {4} {\spellfourthlevelname}
            {5} {\spellfifthlevelname}
            {6} {\spellsixthlevelname}
            {7} {\spellseventhlevelname}
            {8} {\spelleighthlevelname}
            {9} {\spellninthlevelname}
          }
          {#1}
        {~}(
        \str_case_e:nnF {#2}
          {
            {1} {\spelloneslotname}
            {2} {\spelltwoslotsname}
            {3} {\spellthreeslotsname}
            {4} {\spellfourslotsname}
          }
          {#2}
        ) :
      ]
    \DndEmphSpellString{#3}
  }

%% Monster attacks
\tl_new:N \l__dnd_attack_distance_tl
\tl_new:N \l__dnd_attack_type_tl

\keys_define:nn { dnd / monster / attack }
  {
    name .tl_set:N         = \l__name_tl,
    name .value_required:n = true,
    distance .choice:,
    distance .choices:nn =
      { both, melee, ranged }
      { \tl_set:Nn \l__dnd_attack_distance_tl { #1 } },
    distance .initial:n  = both,
    type .choice:,
    type / weapon .code:n = { \tl_set:Nn \l__dnd_attack_type_tl {\weaponname} },
    type / spell .code:n = { \tl_set:Nn \l__dnd_attack_type_tl {\spellname} },
    type .initial:n  = weapon,
    mod .tl_set:N         = \l__mod_tl,
    mod .value_required:n = true,
    reach .tl_set:N         = \l__reach_tl,
    reach .initial:n        = 5,
    reach .value_required:n = true,
    range .tl_set:N         = \l__range_tl,
    range .initial:n        = 20/60,
    range .value_required:n = true,
    targets .tl_set:N         = \l__targets_tl,
    targets .initial:n        = \defaulttargetsname,
    targets .value_required:n = true,
    dmg .tl_set:N         = \l__dmg_tl,
    dmg .value_required:n = true,
    dmg-type .tl_set:N         = \l__dmg_type_tl,
    dmg-type .value_required:n = true,
    plus-dmg .tl_set:N         = \l__plus_dmg_tl,
    plus-dmg .value_required:n = true,
    plus-dmg-type .tl_set:N         = \l__plus_dmg_type_tl,
    plus-dmg-type .value_required:n = true,
    or-dmg .tl_set:N         = \l__or_dmg_tl,
    or-dmg .value_required:n = true,
    or-dmg-when .tl_set:N         = \l__or_dmg_when_tl,
    or-dmg-when .value_required:n = true,
    extra .tl_set:N         = \l__extra_tl,
    extra .value_required:n = true,
  }

\cs_new_protected:Npn \__dnd_monster_reach:
  {
    \reachname\ \l__reach_tl\ \unitsname
  }

\cs_new_protected:Npn \__dnd_monster_range:
  {
    \rangename\ \l__range_tl\ \unitsname
  }

\cs_new_protected:Npn \__dnd_if_plus_dmg:
  {
    \tl_if_empty:NF {\l__plus_dmg_tl}
    { ~ \plusname\ \l__plus_dmg_tl\ \l__plus_dmg_type_tl\ \damagename }
  }

\cs_new_protected_nopar:Npn \__dnd_if_or_dmg:
  {
    \tl_if_empty:NF {\l__or_dmg_tl}
    {
      , ~ \orname\ \l__or_dmg_tl\ \l__dmg_type_tl\ \damagename\ \l__or_dmg_when_tl

      \tl_if_empty:NF {\l__plus_dmg_tl}
        {,}
    }
  }

\cs_new_protected:Npn \__dnd_monster_attack:
  {
    \__dnd_check_for_key:Nnn \l__name_tl {\DndMonsterAttack} {name}
    \__dnd_check_for_key:Nnn \l__mod_tl {\DndMonsterAttack} {mod}

    \begin{DndMonsterAction} {\l__name_tl}~

      \str_case_e:nnF {\l__dnd_attack_distance_tl}
        {
          { melee }
            {
              \textit{ \__dnd_caption:nn {\meleeattackname} {\l__dnd_attack_type_tl} : } ~ \__dnd_caption:nn {\tohitname} {\l__mod_tl}, ~ \__dnd_monster_reach:
            }
          { ranged }
            {
              \textit{ \__dnd_caption:nn {\rangedattackname} {\l__dnd_attack_type_tl} : } ~ \__dnd_caption:nn {\tohitname} {\l__mod_tl}, ~ \__dnd_monster_range:
            }
        }
        {% Melee and Ranged is the default
          \textit{ \__dnd_caption:nn {\meleeorrangedattackname} {\l__dnd_attack_type_tl} : } ~ \__dnd_caption:nn {\tohitname} {\l__mod_tl}, ~ \__dnd_monster_reach:\ \orname\ \__dnd_monster_range:
        }
      , ~ \l__targets_tl. ~
      \textit { \hitname : } ~

      \str_if_empty:NF {\l__dmg_tl} % Don't show any damage if `dmg' is not set.
        {
          \l__dmg_tl\ \l__dmg_type_tl\ \damagename
          \__dnd_if_or_dmg:
          \__dnd_if_plus_dmg:
        }

      % `extra' is any special text that goes after the final damage; do not
      % include the final full stop.
      \l__extra_tl .
    \end{DndMonsterAction}
  }

\NewDocumentCommand {\DndMonsterAttack} {o}
  {
    \group_begin:
      \keys_set:nn { dnd / monster / attack } {#1}
      \__dnd_monster_attack:
    \group_end:
  }

\NewDocumentCommand {\DndMonsterMelee} {o}
  {
    \group_begin:
      \keys_set:nn { dnd / monster / attack } { #1, distance = melee }
      \__dnd_monster_attack:
    \group_end:
  }

\NewDocumentCommand {\DndMonsterRanged} {o}
  {
    \group_begin:
      \keys_set:nn { dnd / monster / attack } { #1, distance = ranged }
      \__dnd_monster_attack:
    \group_end:
  }

%% Legendary Actions

\newlist {DndMonsterLegendaryActions} {description} {1}
\setlist [DndMonsterLegendaryActions]
  {
    font     = \DndFontStatBlockBody,
    labelsep = \l__dnd_space_dim,
    noitemsep,
    topsep   = 6pt plus 2pt minus 2pt,
  }

\NewDocumentCommand {\DndMonsterLegendaryAction} { m m }
  {
    \item [ #1 . ] #2
  }

